/**
 * \file  antiviral_imp.h
 * \brief Definition of Antiviral implementation class.
 *
 * LAV: Lib Anti Virals
 * <http://lav.googlecode.com/>
 * Copyright (C) 2010 Velez Ezequiel, Peralta Andres, FuDePAN
 *
 * This file is part of the LAV project.
 *
 * Contents:       Header file for LAV providing class Antiviral implementation.
 *
 * System:         LAV
 * Language:       C++
 *
 * Author:         Velez Ezequiel
 * E-Mail:         e.s.velez@gmail.com
 *
 * Author:         Peralta Andres
 * E-Mail:         andres.vhd@gmail.com
 *
 * Author:         Boretto Martin
 * E-Mail:         martinboretto@gmail.com
 *
 * Author:         Lenarduzzi Federico
 * E-Mail:         federicolenarduzzi@gmail.com
 * 
 * Author:         Alberione Fernando
 * E-Mail:         falberione037@gmail.com
 *
 * Author:         Ocampo Emanuel
 * E-Mail:         eocampo86@gmail.com
 *
 * LAV is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * LAV is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with LAV.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#ifndef ANTIVIRAL_IMP_H
#define ANTIVIRAL_IMP_H

#include <mili/mili.h>
#include "lav/antiviral.h"
#include "lav/distance_matrix.h"
#include "lav/optimized_container.h"
#include "lav/utils.h"

namespace lav
{

const biopp::NucDistance MAX_VAL = biopp::NucDistance(3);
const biopp::NucDistance ZERO_DISTANCE = biopp::NucDistance(0);
static const float INITIAL_WEIGHT = 1.0f;


/**
 * Antiviral Class
 */
enum AvClass
{
    cPI,    // Class Protease Inhibitor
    cNRTI,  // Class Nucletotide Reverse Transcriptasa Inhibitor
    cNNRTI, // Class Non Nucletotide Reverse Transcriptase Inhibitor
    cComb   // Combinated class.
};


/**
 * Antiviral Type
 */
enum AvType
{
    tRT,    // Type Reverse Transcriptasa
    tPI,    // Type Proteasa Inhibitor
    tComb   // Combinated Type
};


/**
 * This method serialize an avType enum
 * @param out Output stream to serialize the enum
 * @param atype Enum to will be serialized
 * @return Serialize representation of the enum
 */
std::ostream& operator<< (std::ostream& out, const AvType& atype);


/**
 * This method desserialize an avType enum
 * @param is Input stream to desserialize the enum
 * @param atype Enum to will be desserialized
 * @return Desserialize representation of the enum
 */
std::istream& operator>> (std::istream& is, AvType& atype);


/**
 * This method serialize an avType enum
 * @param out Output stream to serialize the enum
 * @param aclass Enum to will be serialized
 * @return Serialize representation of the enum
 */
std::ostream& operator<< (std::ostream& out, const AvClass& aclass);


/**
 * This method desserialize an avType enum
 * @param is Input stream to desserialize the enum
 * @param aclass Enum to will be desserialized
 * @return Desserialize representation of the enum
 */
std::istream& operator>> (std::istream& is, AvClass& aclass);


/**
 * This class implements an antiviral adding an aminoacids sequence, a name, a type, a class
 * and an unique id
 */
class ResisBasedAntiviral: public Antiviral
{

public:

    /** This typedef represents weight for a resistance position */
    typedef double ResistanceWeight;


    /**
     * This struct represents a resistance position <pos, amins, weight>
     * with his funcionality
     */
    struct ResistancePosition
    {
        size_t pos;                 //
        string aminoacids;          //  < pos, aminoacids, weight >
        ResistanceWeight weight;    //


        /**
         * Copy constructor
         */
        ResistancePosition(size_t pos, std::string const amin, ResistanceWeight weight)
            : pos(pos), aminoacids(amin), weight(weight)
        {}


        /**
         * This method serialize a ResistancePosition object
         * @param bis Input stream to serialize the object
         * @param res_position ResistancePosition to will be serialize
         * @return ResistancePosition serialize representation
         */
        friend inline mili::bistream<> operator>> (mili::bistream<>& bis,
                ResistancePosition& res_position);
       
         /**
         * This method returns the minimum genetic distance between triplet and this resistance
         * @param triplet represents a codon
         */
        biopp::NucDistance min_dist_to_position(const biopp::Triplet& triplet) const;

        
        /**
         * This method desserialize a ResistancePosition object
         * @param bos Output stream to desserialize the object
         * @param res_position ResistancePosition to will be desserialize
         * @return ResistancePosition desserialize representation
         */
        friend inline mili::bostream<>& operator<< (mili::bostream<>& bos,
                const ResistancePosition& res_position);
        

        /**
         * This method defines the ResistancePosition's equals operator
         * @param other The ResistancePosition to will be compared
         */
        bool operator==(const ResistancePosition& other) const
        {
            std::string s, s1;
            this->getString(s);
            other.getString(s1);
            return (s == s1);
        }


        /**
         * This method defines the ResistancePosition's equals operator
         * @param other The ResistancePosition to will be compared
         */
        bool operator < (const ResistancePosition& other) const
        {
            bool ret;
            if (weight == other.weight)
	      {
                std::string s, s1;
                this->getString(s);
                other.getString(s1);
                ret = (s < s1);
	      }
	    else
             {
                ret = weight < other.weight;
             }
          return ret;
	}   
        

        /**
         * This method returns a pair with a triplet and the minimal distance with sequence
         * @param sequence Respresents the patient's virus
         * @param min_list A par (triplets, min_dist) will be saved here
         */
        void min_dist_to_resistance(const biopp::PseudoNucSequence& sequence,
                                    TripletVal& min_list) const;

        /**
         * This method expands an aminoacid to his respectives nucleotids
         * @param sequence Respresents the patient's virus
         * @param min_list The nucleotids list will be here
         */
        void expand_aminoacid(const biopp::PseudoNucSequence& sequence, TripletList& triplets) const;

    private:

        /**
         * This method returns the aminoacids belongs to a current position in the parameter
         * @param s The string with the result will be here
         */
        void getString(std::string& s) const
        {
            std::stringstream position;
            position << pos;
            s = "| " + position.str() + " --> " + aminoacids + " |";
        }

        /**
         * This method returns the minimum genetic distance 'it_triplets' and 'pos_triplet' 
         * @param it_triplets triplets of aminoacid
         * @param pos_triplet triplet of sequence
         * @param keep_min_value minimum genetic distance result
         */
        void min_dist_between_triplets(const biopp::TripletsIterator& it_triplets, const biopp::PseudoTriplet& pos_triplet,
                                         OptimizedContainer<TripletSet, biopp::NucDistance, MinimumValues, IncludingZero>& keep_min_value) const;


        /**
         * This method returns the pure triplet of 'pos_triplet' in 'triplets'
         * @param pos_triplet pure triplets of sequence
         * @param it_triplets triplets of current aminoacid
         * @param triplets pure triplets results
         */
        void get_pure_triplets(const biopp::PseudoTriplet& pos_triplet, const biopp::TripletsIterator& it_triplets,
                                TripletList& triplets) const;
    };


    /** This typedef represents a ResistancePosition list {(p1, a1, w1), (p2, a2, w2), ...} */
    typedef std::list <ResistancePosition> ResistancePositionContainer;


    /** This typedef represents a resistances combination of an antiviral group */
    typedef std::list <ResistancePositionContainer> Combination;


    /** This typedef represents all combinations come from mixing two or more antivirals */
    typedef std::list <Combination> CombinationContainer;

    
    /**
     * This struct represents an antiviral's group
     */
    struct ResistancesGroup
    {

        ResistanceWeight minResistanceWeight;       // Minimun weight of the group
        ResistancePositionContainer resistances;   // List of resistances belongs to the current group


        /**
         * Copy constructor
         */
        ResistancesGroup(ResistanceWeight minRes) : minResistanceWeight(minRes)
        {}


        /**
         * There should be at least as many resistances as minimum specified
         */
        inline bool isValid() const;
       


        /**
         * This methos defines the ResistancesGroup's equals operator
         * @param other The ResistanceGroup to will be compared
         */
        inline bool operator==(const ResistancesGroup& other) const;


        /**
         * This method adds a resistance in the group's resistances list
         * @param pos The aminoacid's position
         * @param aminoacids The aminoacid of the resistance
         * @param weight The resistance's weight
         * @return The current group (this) with the resistance added
         */
        ResistancesGroup& add_resistance(size_t pos, const std::string& aminoacids, ResistanceWeight weight)
        {
            mili::assert_throw <string> (pos != 0);
	      SystemError("Incorrect resistance position (ResisBasedAntiviral::add_resistance)");
            
	    mili::insert_into(resistances, ResistancePosition(pos - 1, mili::tolower(aminoacids), weight));
            return *this;
	}


        /**
         * This method generate the resistances partition set for the current group (this)
         * @param partition_set It will contain the power set of the current group
         */
        void generate_power_set(Combination& power_set) const;


        /**
         * This method
         * @param sequence
         * @return
         */
        biopp::NucDistance get_group_distance(const biopp::PseudoNucSequence& sequence) const;


    private:

        /**
         * This method returns the acumulated weight for a given combination of resistance positions
         * @param resistances Contains the resitances positions to the weight check
         * @return acumulated weight
         */
        double get_acumulated_weight(const ResistancePositionContainer& resistances) const
        {
            double acumulatedWeight = 0.0f;
            for (ResistancePositionContainer::const_iterator it = resistances.begin(); it != resistances.end(); ++it)
                acumulatedWeight += (it->weight);

            return acumulatedWeight;
        }


        /**
         * This method return the acumulated distance for a given combination of resistance positions
         * @param resistances Contains the list of resistances to will be check
         * @param sequence Represents the patient's virus
         * @return acumulated distance
         */
        float get_combination_distance(const ResistancePositionContainer& resistances,
                                       const biopp::PseudoNucSequence& sequence) const
        {
            float acumulatedDistance = 0.0f;
            TripletVal min_dist;
            for (ResistancePositionContainer::const_iterator it = resistances.begin(); it != resistances.end(); ++it)
            {
                it->min_dist_to_resistance(sequence, min_dist);
                acumulatedDistance += min_dist.second;
            }
            return acumulatedDistance;
        }

    };


    /** This typedef represents all groups information from an antiviral */
    typedef std::list<ResistancesGroup> ResistancesGroupContainer;


    /**
     * This struct represents a weight predicate to use composed policies into comb engine methods
     */
    struct WeightPredicate
    {

        /**
         * Constructor of struct
         * @param weight min weight
         */
        WeightPredicate(ResistanceWeight weight)
            : min_weight(weight)
        {}


        /**
         * This method checks the acumulated weight of the resistances, to will be used to calculate
         * the group's power set
         * @param elems resistances to will be processed
         * @return true if the resistances weight satisfies the weight condition, false otherwise
         */
        bool check(const std::set<ResistancePosition>& elems) const
        {
            ResistanceWeight acum = 0;
            for (std::set<ResistancePosition>::const_iterator it = elems.begin(); it != elems.end(); ++it)
                acum += (it->weight);
            return (acum >= min_weight);
        }

    private:

        ResistanceWeight min_weight;

    };


    /**
     * This method serialize a ResistancePosition_Container object
     * @param bis Input stream to serialize the object
     * @param res Object to will be serialized
     * @return Serialize representation of the object
     */
    friend inline mili::bistream<>& operator>> (mili::bistream<>& bis, ResistancePositionContainer& res);


    /**
     * This method desserialize a ResistancePosition_Container object
     * @param bos Output stream to desserialize the object
     * @param res Object to will be desserialize
     * @return Desserialize representation of the object
     */
    friend inline mili::bostream<>& operator<< (mili::bostream<>& bos, const ResistancePositionContainer& res);

public:

    ResistancesGroupContainer resGroups;


    ResisBasedAntiviral& _old_add_resistance(size_t pos, const std::string& aminoacid)
    {
        resGroups.front().add_resistance(pos, aminoacid, INITIAL_WEIGHT);                          
        return *this;                                                                   
    }                                                                                   


    /**
     * Constructor
     */
    ResisBasedAntiviral(const ResisBasedAntiviral& other)
        : Antiviral(other), resGroups(other.resGroups)
    {}


    /**
     * Copy constructor with all parameters
     */
    ResisBasedAntiviral(const std::string& name, size_t id, AvType avtype, AvClass avclass)
        : Antiviral(name)
    {
        set_attrib("id", id);
        set_attrib("class", avclass);
        set_attrib("Type", avtype);
    }


    /**
     * This method adds an attribute into the antiviral's implementation
     * @param attrib Attribute to will be added
     * @param value Attribute's value
     * @return
     */
    ResisBasedAntiviral& add_attribute(const std::string& attrib, const std::string& value)
    {
        set_attrib(attrib, value);
        return *this;
    }


    /**
     * This method prints all information of an antiviral
     */
    void print_antiviral() const
    {
        print_attributes();
        std::cout << "\tResistances" << std::endl;
        print_resistances();
        std::cout << " ";
    }


    /**
     * This method prints all information of an antiviral's attribute
     */
    void print_attributes() const
    {
        AntiviralsNames av_name;
        this->get_attrib("name", av_name);
        std::cout << "Name: " << av_name << std::endl;
    }


    /**
     * This method defines the ResisBasedAntiviral's equals operator
     * @param other ResisBasedAntiviral to will be check
     */

   
    /**
     * This method returns the minimum genetic distance between the mutant sequence and this antiviral
     * @param sequence_mutant represents patient's virus
     */
    virtual biopp::NucDistance min_dist_to_antiviral(const PossibleMutants::TripletPositionContainer& sequence_mutant) const;

   
    inline bool operator==(const ResisBasedAntiviral& other) const;
 

    /**
     * This method sets the list of resitances 
     * @param resist
     */
    void set_resistances(const ResistancePositionContainer& resist)
    {
        mili::copy_container(resist, resGroups.front().resistances);
    }


    /**
     * This method returns the list of resistances
     * @param resist The result will be here
     */
    void get_resistances(ResistancePositionContainer& resist) const
    {
        mili::copy_container(resGroups.front().resistances, resist);
    }


    /**
     * This method prints all antiviral's resistances
     */
    void print_resistances() const;


    /**
     * This method combines two antivirals (this with other)
     * @param other Antiviral to will be combined
     */
    void combine_with(ResisBasedAntiviral& other) const;


    /**
     * This method checks the antiviral's applicanse with de sequence
     * @param sequence Represents the patient's virus
     * @return Distance between the current antiviral and sequence
     */
    biopp::NucDistance applies(const biopp::PseudoNucSequence& sequence) const;


    /**
     * This method applies an antiviral (this) to the sequence and generate mutants
     * @param sequence Represents the patient's virus
     * @param mutations It will contain the mutants generated
     */
    void mutate_sequence(const biopp::PseudoNucSequence& sequence,
                         MutantDist& mutations) const;

private:
     /**
     * This method returns the minimum genetic distance between 
     * the current triplet (of sequence) and the current group (of current antiviral)
     * @param it_group current group 
     * @param it_sequence current triplet of sequence
     * @param m
     */
     void combine_currently(const mili::CAutonomousIterator<ResisBasedAntiviral::ResistancePositionContainer>& it,
					    ResisBasedAntiviral& other) const;
    /**
     * This method returns the minimum genetic distance between 
     * the current triplet (of sequence) and the current group (of current antiviral)
     * @param it_group current group 
     * @param it_sequence current triplet of sequence
     * @param min_dist minimum genetic distance
     */
    biopp::NucDistance min_dist_to_group(const ResistancesGroupContainer::const_iterator& it_group,
                            const PossibleMutants::TripletPositionContainer::const_iterator& it_sequence) const;
    

    /**
     * This method adds the aminoacids 'amin_temp' to 'aminoacids' 
     * @param amin_temp aminoacids temporary
     * @param aminoacids aminoacids result
     */
    void add_aminoacids(const std::string& amin_temp, std::string& aminoacids) const;
};
 
}//end namespace

 #define ANTIVIRAL_IMP_INLINE_H
 #include "antiviral_imp_inline.h"
 #undef ANTIVIRAL_IMP_INLINE_H

#endif
